package drds.plus.parser.abstract_syntax_tree.expression;

import drds.plus.parser.abstract_syntax_tree.expression.arithmetic.ArithmeticCalculator;
import drds.plus.parser.abstract_syntax_tree.expression.primary.literal.LiteralBoolean;
import drds.plus.parser.lexer.Lexer;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;

public class Evals {

    private static final Map<Class<? extends Number>, Integer> classMap = new HashMap<Class<? extends Number>, Integer>(5);
    private static final int CLASS_MAP_FLOAT = 1;
    private static final int CLASS_MAP_DOUBLE = 2;
    private static final int CLASS_MAP_LONG = 3;
    private static final int CLASS_MAP_BIG_ING = 4;
    private static final int CLASS_MAP_BIG_DECIMAL = 5;

    static {
        classMap.put(Float.class, CLASS_MAP_FLOAT);
        classMap.put(Double.class, CLASS_MAP_DOUBLE);
        classMap.put(Long.class, CLASS_MAP_LONG);
        classMap.put(BigDecimal.class, CLASS_MAP_BIG_DECIMAL);
        classMap.put(BigInteger.class, CLASS_MAP_BIG_ING);
    }

    //
    private static final int NUM_INT = 1;
    private static final int NUM_LONG = 2;
    private static final int NUM_BIG_INTEGER = 3;
    private static final int NUM_BIG_DECIMAL = 4;

    public static boolean obj2bool(Object obj) {
        if (obj == LiteralBoolean.TRUE) {
            return true;
        }
        if (obj == LiteralBoolean.FALSE) {
            return false;
        }
        if (obj instanceof Boolean) {
            return (Boolean) obj;
        }
        Number num;
        if (obj instanceof String) {
            num = Evals.string2Number((String) obj);
        } else {
            num = (Number) obj;
        }
        Integer classType = classMap.get(num.getClass());
        if (classType == null) {
            return num.intValue() != 0;
        }
        switch (classType) {
            case CLASS_MAP_FLOAT:
                return num.floatValue() != 0f;
            case CLASS_MAP_DOUBLE:
                return num.doubleValue() != 0d;
            case CLASS_MAP_LONG:
                return num.longValue() != 0L;
            case CLASS_MAP_BIG_ING:
                return BigInteger.ZERO.compareTo((BigInteger) num) != 0;
            case CLASS_MAP_BIG_DECIMAL:
                return BigDecimal.ZERO.compareTo((BigDecimal) num) != 0;
            default:
                throw new IllegalArgumentException("unsupported number type: " + num.getClass());
        }
    }


    public static Object calculate(ArithmeticCalculator cal, Number n1, Number n2) {
        try {
            if (n1 == null || n2 == null) {
                return null;
            }
            if (n1 instanceof Integer) {
                return cal.calculate((Integer) n1, (Integer) n2);
            }
            if (n1 instanceof Long) {
                return cal.calculate((Long) n1, (Long) n2);
            }
            if (n1 instanceof BigInteger) {
                return cal.calculate((BigInteger) n1, (BigInteger) n2);
            }
            if (n1 instanceof BigDecimal) {
                return cal.calculate((BigDecimal) n1, (BigDecimal) n2);
            }

        } catch (Throwable e) {
            return Expression.UNEVALUATABLE;// 不支持的话推到数据库去做，这里不抛异常
        }

        return Expression.UNEVALUATABLE;
    }

    /**
     * @param obj1 class of String or Number
     */
    public static Pair<Number, Number> convertNum2SameLevel(Object obj1, Object obj2) {
        Number n1, n2;
        if (obj1 instanceof String) {
            n1 = string2Number((String) obj1);
        } else {
            n1 = (Number) obj1;
        }
        if (obj2 instanceof String) {
            n2 = string2Number((String) obj2);
        } else {
            n2 = (Number) obj2;
        }
        if (n1 == null || n2 == null) {
            return new Pair<Number, Number>(n1, n2);
        }
        int l1 = getNumberLevel(n1.getClass());
        int l2 = getNumberLevel(n2.getClass());
        if (l1 > l2) {
            n2 = upTolevel(n2, l1);
        } else if (l1 < l2) {
            n1 = upTolevel(n1, l2);
        }
        return new Pair<Number, Number>(n1, n2);
    }

    private static Number upTolevel(Number num, int level) {
        switch (level) {
            case NUM_INT:
                if (num instanceof Integer) {
                    return num;
                }
                return num.intValue();
            case NUM_LONG:
                if (num instanceof Long) {
                    return num;
                }
                return num.longValue();
            case NUM_BIG_INTEGER:
                if (num instanceof BigInteger) {
                    return num;
                }
                return new BigInteger(num.toString());
            case NUM_BIG_DECIMAL:
                if (num instanceof BigDecimal) {
                    return num;
                }
                return new BigDecimal(num.toString());
            default:
                throw new IllegalArgumentException("unsupported number level: " + level);
        }
    }

    private static int getNumberLevel(Class<?> aClass) {
        if (Integer.class.isAssignableFrom(aClass)) {
            return NUM_INT;
        }
        if (Long.class.isAssignableFrom(aClass)) {
            return NUM_LONG;
        }
        if (BigInteger.class.isAssignableFrom(aClass)) {
            return NUM_BIG_INTEGER;
        }
        if (BigDecimal.class.isAssignableFrom(aClass)) {
            return NUM_BIG_DECIMAL;
        }
        throw new IllegalArgumentException("unsupported number class: " + aClass);
    }

    public static Number string2Number(String string) {
        if (string == null)
            return null;
        try {
            return new Integer(string);
        } catch (Exception e) {
        }
        try {
            return new Long(string);
        } catch (Exception e) {
        }
        Lexer lexer = new Lexer(string);
        switch (lexer.token()) {
            case LITERAL_NUM_INTEGER:
                return lexer.integerValue();
            case LITERAL_NUM_DECIMAL:
                return lexer.decimalValue();
            default:
                throw new IllegalArgumentException("unrecognized number: " + string);
        }
    }

}
